package com.tarena.lbs.content.service.impl;

import com.alibaba.fastjson2.JSON;
import com.tarena.lbs.base.protocol.exception.BusinessException;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.common.passport.principle.UserPrinciple;
import com.tarena.lbs.common.security.utils.LbsSecurityContenxt;
import com.tarena.lbs.content.service.ArticleService;
import com.tarena.lbs.pojo.content.docs.ArticleDoc;
import com.tarena.lbs.pojo.content.dto.ArticleContentDTO;
import com.tarena.lbs.pojo.content.param.ArticleCategoryParam;
import com.tarena.lbs.pojo.content.query.ArticleQuery;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.utils.DateUtils;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.collapse.CollapseBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;

@Slf4j
@Service
public class ArticleServiceImpl implements ArticleService {
    @Autowired
    private RestHighLevelClient client;
    @Override
    public PageResult<ArticleContentDTO> queryList(ArticleQuery query) throws BusinessException {
        log.info("查询文章分页列表的入参:{}",query);
        //查询结构 重点构造bool条件
        SearchRequest request=new SearchRequest("lbs_content");
        //封装请求参数 构造器
        SearchSourceBuilder builder=new SearchSourceBuilder();
        //根据query中的数据 创建多个子条件的bool条件
        //使用source区分 当前查询是手机小程序查询=1 还是后台管理的查询=2
        Integer source = query.getSource();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        UserPrinciple loginToken = LbsSecurityContenxt.getLoginToken();
        if (loginToken==null){
            throw new BusinessException("101","登录用户无法获取");
        }
        Integer userId=loginToken.getId();
        if (source==1){
            //小程序查询 两个字条件
            appQuery(query, boolQueryBuilder);
        }else{
            //后台查询条件
            backQuery(query, userId, boolQueryBuilder);
        }
        //分页
        int from=(query.getPageNo()-1)*query.getPageSize();
        builder.query(boolQueryBuilder);
        builder.from(from);
        builder.size(query.getPageSize());
        //排序 最好把最新的文章展示在列表上层 尤其是 后台管理
        builder.sort("createTime", SortOrder.DESC);
        request.source(builder);
        log.info("bool query 封装结果:{}",boolQueryBuilder);
        //查询结果 封装返回值 totalHits 分页数据
        PageResult<ArticleContentDTO> pageResult=new PageResult<>();
        try{
            //封装返回类型
            packgeResult(query, request, pageResult);
            return pageResult;
        }catch (Exception e){
            log.error("查询出现异常",e);
            throw new BusinessException("106","查询异常");
        }
    }

    //封装返回类型
    private void packgeResult(ArticleQuery query, SearchRequest request, PageResult<ArticleContentDTO> pageResult) throws IOException {
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        long total=response.getHits().getTotalHits().value;
        log.info("查询结果:{}",response);
        List<ArticleContentDTO> dtos=new ArrayList<>();
        if (total>0){
            //查询结果 分页结果的文档数组
            SearchHit[] hits = response.getHits().getHits();
            for (SearchHit hit : hits) {
                //hit包含文档的json数据
                String docJson=hit.getSourceAsString();
                ArticleContentDTO articleContentDTO = JSON.parseObject(docJson, ArticleContentDTO.class);
                hit.getIndex();
                hit.getType();
                hit.getScore();
                String id = hit.getId();
                articleContentDTO.setId(id);
                dtos.add(articleContentDTO);
            }
        }
        pageResult.setTotal(total);
        pageResult.setObjects(dtos);
        pageResult.setPageNo(query.getPageNo());
        pageResult.setPageSize(query.getPageSize());
    }

    //小程序ES查询
    private static void appQuery(ArticleQuery query, BoolQueryBuilder boolQueryBuilder) {
        //1 地理位置
        GeoPoint geoPoint = new GeoPoint(Double.valueOf(query.getLatitude()), Double.valueOf(query.getLongitude()));
        GeoDistanceQueryBuilder geoQuery = QueryBuilders.geoDistanceQuery("location");
        geoQuery.distance(50D, DistanceUnit.KILOMETERS).point(geoPoint);
        boolQueryBuilder.must(geoQuery);
        //2 label值相等 termQuery对比相等
        TermQueryBuilder termQuery = QueryBuilders.termQuery("articleLabel", query.getArticleLabel());
        boolQueryBuilder.must(termQuery);
    }

    //后台商户的ES查询
    private static void backQuery(ArticleQuery query, Integer userId, BoolQueryBuilder boolQueryBuilder) {
        //1. 判断 搜索 是否 是当前登录用户的文章
        TermQueryBuilder userIdQuery = QueryBuilders.termQuery("userId", userId);
        boolQueryBuilder.must(userIdQuery);
        //2. 可能是空 非空的时候 才构造子条件
        String articleTitle = query.getArticleTitle();
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(articleTitle)){
            //ariticleTitle 是text类型可以做全文匹配搜索 matchQuery
            MatchQueryBuilder articleTitleQuery = QueryBuilders.matchQuery("articleTitle", articleTitle);
            boolQueryBuilder.must(articleTitleQuery);
        }
        //文章状态 //上线下线
        List<Integer> articleStatus = query.getArticleStatus();
        if (com.alibaba.nacos.common.utils.CollectionUtils.isNotEmpty(articleStatus)){
            //循环 状态 每个状态 拼接子条件 都是或的关系
            articleStatus.stream().forEach(status->{
                TermQueryBuilder statusQuery = QueryBuilders.termQuery("articleStatus", status);
                //状态如果是多个值 0 1 查询的文章不可能同时满足
                boolQueryBuilder.must(statusQuery);
            });
        }
        //更新时间段
        updateTime(query, boolQueryBuilder);
    }

    private static void updateTime(ArticleQuery query, BoolQueryBuilder boolQueryBuilder) {
        //更新时间段
        Date updateStartTime = query.getUpdateStartTime();
        Date updateEndTime = query.getUpdateEndTime();
        if (updateStartTime!=null && updateEndTime!=null){
            //updateTime字段 date类型 可以做范围range查询
            RangeQueryBuilder updateTimeQuery = QueryBuilders.rangeQuery("updateTime").gte(updateStartTime).lte(updateEndTime);
            boolQueryBuilder.must(updateTimeQuery);
        }
    }


    @Override
    public void addArticle(ArticleCategoryParam param) throws BusinessException{
        //1.新增创建文档,主备request
        IndexRequest request = new IndexRequest("lbs_content");
        ArticleDoc articleDoc = null;
        //2.根据参数封装对象,写入请求中
        articleDoc = new ArticleDoc();
        BeanUtils.copyProperties(param,articleDoc);
        //2.2 userId 创建如的Id值
        UserPrinciple userPrinciple = LbsSecurityContenxt.getLoginToken();
        if (userPrinciple==null){
            throw new BusinessException("101","用户解析认证失败");
        }
        Integer userId = userPrinciple.getId();
        articleDoc.setUserId(userId);
        //2.3设置地理位置
        String location= param.getLatitude()+","+param.getLongitude();
        articleDoc.setLocation(location);
        //2.4设置时间数据
        Date now = new Date();
        String timeStr = DateUtils.formatDate(now, "yyyy-MM-dd HH:mm:ss");
        articleDoc.setCreateTime(timeStr);
        articleDoc.setUpdateTime(timeStr);

        //2.5将这个doc对象转化成请求恶意携带的数据
        String jsonData = JSON.toJSONString(articleDoc);
        request.source(jsonData, XContentType.JSON);
        //3.client客户端,新增到ES中
        try {
            client.index(request,RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("新增文章:{},失败",articleDoc,e);
        }
    }

    @Override
    public HashSet<String> getArticleLables(ArticleQuery query) {
        //1. 封装返回结果
        HashSet<String> labLes = new HashSet<>();
        //2.ES标准查询流程
        SearchRequest EsResponse = EsSearch(query);
        //3.查询获取响应
        getResponse(EsResponse, labLes);
        return labLes;
    }

    @Override
    public ArticleContentDTO getArticleDetail(String id) throws BusinessException {
        //1.利用id 注释获取文档 请求对象GetRequest 提供文档Id
        GetRequest request = new GetRequest("lbs_content", id);
        //2.client调用Get方法 获取文档
        try {
            GetResponse response = client.get(request, RequestOptions.DEFAULT);
            boolean exists = response.isExists();
            ArticleContentDTO dto =null;
            if (exists){
                //转化
                dto = JSON.parseObject(response.getSourceAsString(), ArticleContentDTO.class);
                dto.setId(id);
            }
            return dto;
        } catch (IOException e) {
            log.error("查询文档id:{},失败",id,e);
        }
        return null;
    }

    //获取响应
    private void getResponse(SearchRequest searchRequest, HashSet<String> labLes) {
        try {
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            //拿到响应结果的集合
            SearchHit[] hits = response.getHits().getHits();
            log.info("查询结果命中:{}",response.getHits().getTotalHits().value);
            if (response.getHits().getTotalHits().value>0){
                for (SearchHit hit : hits) {
                    //hit 就包含一个doc对象全属性
                    DocumentField articleLabel = hit.getFields().get("articleLabel");
                    String labelValue = articleLabel.getValue();
                    labLes.add(labelValue);
                }
            }
        } catch (IOException e) {
            log.error("查询标签集合出现异常...",e);
        }
    }

    //ES查询
    private  SearchRequest EsSearch(ArticleQuery query) {
        //2.1创建一个搜索查询request
        SearchRequest searchRequest = new SearchRequest("lbs_content");
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //2.2 查询的request定义各种查询的条件 范围50公里 去重 分页 取前20个标签
        GeoDistanceQueryBuilder geoQuery = QueryBuilders.geoDistanceQuery("location");
        //中心点
        GeoPoint geoPoint = new GeoPoint(Double.parseDouble(query.getLatitude()), Double.parseDouble(query.getLongitude()));
        //定义查询距离
        geoQuery.distance(500, DistanceUnit.KILOMETERS).point(geoPoint);
        //在Builder中添加Query
        builder.query(geoQuery);
        //去重设置
        CollapseBuilder distinct = new CollapseBuilder("articleLabel");
        builder.collapse(distinct);
        //分页
        builder.size(20);
        searchRequest.source(builder);
        return searchRequest;
    }


}
